package com.bleeth.flow.core.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.text.csv.CsvRow;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.db.Entity;
import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ES查询帮助类
 *
 * @author BleethNie
 * @version 1.0
 * @date 2021-10-18 17:15
 **/
@Slf4j
public class ESQueryUtil {

    private static final String ES_SETTING_NAME = "spring.elasticsearch.rest.uris:";

    public static final String ES_INDEX_TYPE_SOURCE = "source";

    public static final String ES_INDEX_TYPE_NODE = "node";


    String _idStr = "";

    /**
     * @param esUrl     示例:http://192.168.0.2:9200
     * @param indexName
     * @param type
     * @return
     */
    public List<String> getFields(String esUrl, String indexName, String type) {
        String getUrl = StrUtil.format("{}/{}", esUrl, indexName);
        HttpRequest httpRequest = HttpRequest.get(getUrl);
        String resultJSON = httpRequest.header("Connection", "close")
                .keepAlive(false)
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        JSONObject rootJSON = JSONUtil.parseObj(resultJSON);
        JSONObject errorJSON = rootJSON.getJSONObject("error");
        if (errorJSON != null) {
            return null;
        }
        JSONObject indexObject = rootJSON.getJSONObject(indexName);
        JSONObject mappingsObject = indexObject.getJSONObject("mappings");
        JSONObject sourceObject = mappingsObject.getJSONObject(type);
        JSONObject propertiesObject = sourceObject.getJSONObject("properties");
        List<String> headerList = new ArrayList<>();
        propertiesObject.entrySet().stream().forEach(entry -> {
            String key = entry.getKey();
            headerList.add(key);
        });

        return headerList;
    }

    /**
     *
     * @param esUrl
     * @param index
     * @return
     */
    public Integer countIndex(String esUrl,String index) {
        String postUrl = StrUtil.format("{}/{}/_search", esUrl, index);

        String queryBody = "{\"query\":{\"bool\":{\"must\":[{\"match_all\":{}}]}},\"from\":0,\"size\":0}";

        String response = HttpRequest.post(postUrl)
                .header("Connection", "close")
                .keepAlive(false)
                .timeout(20000)
                .body(queryBody)
                .execute()
                .body();

        JSONObject object = JSONUtil.parseObj(response);
        if (object.containsKey("error")) {
            return null;
        }
        Long count = parseHitsCount(object);
        Integer num = Convert.toInt(count);
        return num;
    }


    /**
     * 批量删除
     *
     * @param esUrl
     * @param indexName
     * @param type
     * @param _idList
     */
    public void deleteEsRow(String esUrl, String indexName, String type, List<String> _idList) {
        String metaTemplate = "{ \"delete\": { \"_index\": \"{}\", \"_type\": \"{}\", \"_id\": \"{}\" }}";
        StrBuilder strBuilder = new StrBuilder();
        for (int i = 0; i < _idList.size(); i++) {
            String _id = _idList.get(i);
            String meta = StrUtil.format(metaTemplate, indexName, type, _id);
            strBuilder.append(meta).append("\r\n");
        }
        String postUrl = StrUtil.format("{}/_bulk", esUrl);
        HttpRequest httpRequest = HttpRequest.post(postUrl);
        String resultJSON = httpRequest.header("Connection", "close")
                .keepAlive(false)
                .body(strBuilder.toString())
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
    }


    /**
     * 检查索引是否存在，存在为true
     *
     * @param esUrl
     * @param indexName
     * @return
     */
    public boolean checkIndex(String esUrl, String indexName) {
        String getUrl = StrUtil.format("{}/{}", esUrl, indexName);
        HttpRequest httpRequest = HttpRequest.get(getUrl);
        String resultJSON = httpRequest.header("Connection", "close")
                .keepAlive(false)
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        JSONObject rootJSON = JSONUtil.parseObj(resultJSON);
        JSONObject errorJSON = rootJSON.getJSONObject("error");
        if (errorJSON == null) {
            return true;
        }
        return false;
    }


    /**
     * 需要hanlp 分词插件,如果使用其它分词器，则修改里面的json
     *
     * @param esUrl
     * @param indexName
     * @param type
     * @param headerMap 字段类型
     * @return
     */
    public boolean createMapping(String esUrl, String indexName, String type, Map<String, String> headerMap) {
        //创建mappping
        String putUrl = esUrl + "/" + indexName;

        JSONObject root = new JSONObject();
        JSONObject mappingObject = new JSONObject();
        JSONObject typeObject = new JSONObject();
        JSONObject propertiesObject = new JSONObject();
        for (int i = 0; i < headerMap.size(); i++) {
            String field = headerMap.get(i);
            String fieldType = "{\"analyzer\":\"hanlp_index\",\"type\":\"text\",\"fields\":{\"keyword\":{\"type\":\"keyword\"}}}";
            JSONObject fieldObj = JSONUtil.parseObj(fieldType);
            propertiesObject.set(field, fieldObj);
        }

        //添加系统字段
        String dateType = "{\"format\":\"yyyy-MM-dd HH:mm:ss\",\"type\":\"date\"}";
        JSONObject fieldObj = JSONUtil.parseObj(dateType);
        propertiesObject.set("_updateTime", fieldObj);


        typeObject.set("properties", propertiesObject);
        mappingObject.set(type, typeObject);
        root.set("mappings", mappingObject);


        String resultJSON = HttpRequest
                .put(putUrl)
                .header("Connection", "close")
                .keepAlive(false)
                .body(root.toStringPretty())
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        JSONObject rootJSON = JSONUtil.parseObj(resultJSON);
        JSONObject errorJSON = rootJSON.getJSONObject("error");
        if (errorJSON == null) {
            return false;
        }
        return true;
    }

    /**
     * 提交数据
     *
     * @param esUrl
     * @param indexName
     * @param type
     * @param header
     * @param csvRowList
     * @return
     */
    public boolean commitData(String esUrl, String indexName, String type, List<String> header, List<CsvRow> csvRowList) {
        //创建mappping
        String postUrl = StrUtil.format("{}/_bulk", esUrl);
        String metaTemplate = "{\"index\": {\"_index\": \"{}\", \"_type\": \"{}\", \"_id\": \"{}\"}}";
        String docTemplate = "{}";
        StrBuilder strBuilder = new StrBuilder();

        DateTime current = DateUtil.date();

        for (CsvRow csvRow : csvRowList) {
            String _idStr = "";
            JSONObject docObject = new JSONObject();
            JSONObject json = new JSONObject();
            for (int i = 0; i < header.size(); i++) {
                String field = header.get(i);
                String value = csvRow.getByName(field);
                _idStr = _idStr + value;
                docObject.set(field, value);
            }

            //系统字段
            docObject.set("_updateTime", current.toString());


            String _id = SecureUtil.md5(_idStr);
            String meta = StrUtil.format(metaTemplate, indexName, type, _id);
            String doc = StrUtil.format(docTemplate, docObject.toString());
            strBuilder.append(meta).append("\r\n").append(doc).append("\r\n");
        }

        String resultJSON = HttpRequest.post(postUrl)
                .header("Connection", "close")
                .keepAlive(false)
                .body(strBuilder.toString())
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        return true;
    }


    /**
     * 创建索引
     *
     * @param esUrl
     * @param indexName
     * @return
     */
    public boolean createIndex(String esUrl, String indexName) {
        String putUrl = StrUtil.format("{}/{}", esUrl, indexName);
        String resultJSON = HttpRequest.put(putUrl)
                .header("Connection", "close")
                .keepAlive(false)
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        JSONObject rootJSON = JSONUtil.parseObj(resultJSON);
        JSONObject errorJSON = rootJSON.getJSONObject("error");
        if (errorJSON == null) {
            return false;
        }
        return true;
    }


    public JSONArray bulkQuery(String queryName, Map<String, Object> queryParam) {
        JSONObject rootJSON = singleQuery(queryName, queryParam);
        JSONArray responseArray = rootJSON.getJSONArray("responses");
        if (responseArray == null || responseArray.isEmpty()) {
            log.info("结果参数：" + rootJSON.toStringPretty());
            throw new RuntimeException(StrUtil.format("ES查询异常,查询结果出错,请检查查询语句是否正确"));
        }
        return responseArray;
    }

    public JSONArray bulkQuery(String queryName, List<Map<String, Object>> queryParams) {
        JSONObject rootJSON = singleQuery(queryName, queryParams, null, true);
        if (rootJSON == null) {
            return null;
        }
        JSONArray responseArray = rootJSON.getJSONArray("responses");
        if (responseArray == null || responseArray.isEmpty()) {
            log.info("结果参数：" + rootJSON.toStringPretty());
            throw new RuntimeException(StrUtil.format("ES查询异常,查询结果出错,请检查查询语句是否正确"));
        }
        return responseArray;
    }

    public JSONObject bulkAction(String queryName, List<Map<String, Object>> queryParams) {
        JSONObject rootJSON = singleQuery(queryName, queryParams, null, true);
        boolean error = rootJSON.getBool("errors");
        if (error) {
            log.info("结果参数：" + rootJSON.toStringPretty());
            throw new RuntimeException(StrUtil.format("ES查询异常,查询结果出错,请检查查询语句是否正确"));
        }
        return rootJSON;
    }


    /**
     * 使用批量查询语句，查询出的第1个结果
     *
     * @param queryName
     * @param queryParam
     * @return
     */
    public JSONObject bulkSingleQuery(String queryName, Map<String, Object> queryParam) {
        JSONArray responsesArray = bulkQuery(queryName, queryParam);
        return responsesArray.getJSONObject(0);
    }

    public JSONObject singleQuery(String queryName, Map<String, Object> queryParam) {
        List<Map<String, Object>> queryParams = CollUtil.newArrayList(queryParam);
        JSONObject rootJSON = singleQuery(queryName, queryParams, null, true);
        return rootJSON;
    }

    public JSONObject singleGetQuery(String queryName, Map<String, Object> queryParam) {
        List<Map<String, Object>> queryParams = CollUtil.newArrayList(queryParam);
        return singleGetQuery(queryName, queryParams);
    }


    public JSONObject singleGetQuery(String queryName, List<Map<String, Object>> queryParams) {
        return singleQuery(queryName, queryParams, null, false);
    }


    /**
     * @param queryName
     * @param queryParams
     * @param postQuery
     * @return
     */
    public JSONObject singleQuery(String queryName, List<Map<String, Object>> queryParams, String queryUrl, boolean postQuery) {
        Entity entity = null;
        if (entity == null) {
            throw new RuntimeException(StrUtil.format("ES查询异常,没有从系统中找到对应的查询语句，queryName = {}", queryName));
        }

        String postUrl = entity.getStr("url");
        if (StrUtil.isNotEmpty(queryUrl)) {
            postUrl = queryUrl;
        }
        String queryContentTemplate = entity.getStr("template");

        Map<String, String> paramMap = new HashMap<>();
        StringBuilder strBuilder = new StringBuilder();

        if (queryParams == null || CollUtil.isEmpty(queryParams)) {
            strBuilder.append(queryContentTemplate);
        } else {
            for (Map<String, Object> record : queryParams) {
                String template = StrUtil.format(queryContentTemplate, record);
                strBuilder.append(template).append("\n");
            }
        }

        HttpRequest httpRequest;
        if (postQuery) {
            httpRequest = HttpRequest.post(postUrl);
        } else {
            httpRequest = HttpRequest.get(postUrl);
        }
        String resultJSON = httpRequest.header("Connection", "close")
                .keepAlive(false)
                .body(strBuilder.toString())
                .timeout(20000)//超时，毫秒
                .execute().body();

        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            log.error("查询参数：" + strBuilder.toString());
            log.error("结果参数：" + resultJSON);
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        JSONObject rootJSON = JSONUtil.parseObj(resultJSON);
        JSONObject errorJSON = rootJSON.getJSONObject("error");
        if (errorJSON != null) {
            log.error("查询参数：" + strBuilder.toString());
            log.error("结果参数：" + resultJSON);
            throw new RuntimeException(StrUtil.format("ES查询异常,查询结果出错,请检查查询语句是否正确"));
        }
        return rootJSON;
    }


    /**
     * @param response
     * @return
     */
    public Long parseHitsCount(JSONObject response) {
        JSONObject hitsObject = response.getJSONObject("hits");
        return hitsObject.getLong("total");
    }


    /**
     * @param response
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> parseHit(JSONObject response, Class<T> clazz) {
        JSONObject hitsObject = response.getJSONObject("hits");
        JSONArray hitsArray = hitsObject.getJSONArray("hits");
        if (hitsArray == null || hitsArray.size() == 0) {
            return new ArrayList<>();
        }
        List<T> ts = new ArrayList<>();
        for (int i = 0; i < hitsArray.size(); i++) {
            JSONObject hitObject = hitsArray.getJSONObject(i);
            ts.add(hitObject.getBean("_source", clazz));
        }
        return ts;
    }

    /**
     * @param response
     * @return
     */
    public List<JSONObject> parseHit(JSONObject response) {
        JSONObject hitsObject = response.getJSONObject("hits");
        JSONArray hitsArray = hitsObject.getJSONArray("hits");
        if (hitsArray == null || hitsArray.size() == 0) {
            return new ArrayList<>();
        }
        List<JSONObject> ts = new ArrayList<>();
        for (int i = 0; i < hitsArray.size(); i++) {
            JSONObject hitObject = hitsArray.getJSONObject(i);
            JSONObject sourceObject = hitObject.getJSONObject("_source");
            String _id = hitObject.getStr("_id");
            sourceObject.set("esId", _id);
            ts.add(sourceObject);
        }
        return ts;
    }


    /**
     * @param response
     * @param aggName
     * @return
     */
    public JSONArray parseBuckets(JSONObject response, String aggName) {
        JSONObject agg;
        JSONObject aggObject = response.getJSONObject("aggs");
        JSONObject aggregationsObject = response.getJSONObject("aggregations");
        if (aggObject == null && aggregationsObject == null) {
            log.error("结果参数：" + response.toStringPretty());
            throw new RuntimeException(StrUtil.format("ES查询异常,聚合结果异常"));
        }
        agg = aggObject != null ? aggObject : aggregationsObject;
        JSONObject aggResultObject = agg.getJSONObject(aggName);
        return aggResultObject.getJSONArray("buckets");
    }


    public Integer parseDistinct(JSONObject response) {
        JSONObject agg;
        JSONObject aggObject = response.getJSONObject("aggs");
        JSONObject aggregationsObject = response.getJSONObject("aggregations");
        if (aggObject == null && aggregationsObject == null) {
            log.error("结果参数：" + response.toStringPretty());
            throw new RuntimeException(StrUtil.format("ES查询异常,聚合结果异常"));
        }
        agg = aggObject != null ? aggObject : aggregationsObject;
        JSONObject distinctObject = agg.getJSONObject("distinct");
        return distinctObject.getInt("value");
    }


    public boolean removeIndex(String esUrl, String indexName) {
        //创建mappping
        String deleteUrl = esUrl + "/" + indexName;

        String resultJSON = HttpRequest
                .delete(deleteUrl)
                .header("Connection", "close")
                .keepAlive(false)
                .timeout(20000)//超时，毫秒
                .execute().body();
        boolean isJSON = JSONUtil.isJson(resultJSON);
        if (!isJSON) {
            throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
        }
        JSONObject rootJSON = JSONUtil.parseObj(resultJSON);
        JSONObject errorJSON = rootJSON.getJSONObject("error");
        if (errorJSON == null) {
            return false;
        }
        return true;

    }

    public String scrollSearch(String esUrl,String index, String scrollId) {
        if (StrUtil.isEmpty(scrollId)) {
            String getUrl = StrUtil.format("{}/{}/_search?scroll=2m", esUrl, index);
            String queryTemplate = "{\"query\":{\"match_all\":{}},\"sort\":[\"_doc\"],\"size\":1000}";

            String response = HttpRequest.get(getUrl)
                    .header("Connection", "close")
                    .body(queryTemplate)
                    .keepAlive(false)
                    .timeout(20000)
                    .execute()
                    .body();
            return response;
        }

        String getUrl = StrUtil.format("{}/{}/_search/scroll", esUrl, index);
        String queryTemplate = "{\"scroll\":\"2m\",\"scroll_id\":\"{}\"}";
        String query = StrUtil.format(queryTemplate, scrollId);
        String response = HttpRequest.get(getUrl)
                .header("Connection", "close")
                .body(query)
                .keepAlive(false)
                .timeout(20000)
                .execute()
                .body();
        return response;
    }


    public void commitNode(String esUrl,JSONObject nodeObject) {

        nodeObject.entrySet().stream().forEach(entry -> {
            String ontologyId = entry.getKey();
            JSONArray dataArray = (JSONArray) entry.getValue();
            String index = "node_" + ontologyId;
            String type = "node";

            String postUrl = StrUtil.format("{}/_bulk", esUrl);
            String metaTemplate = "{\"update\": {\"_index\": \"{}\", \"_type\": \"{}\", \"_id\": \"{}\"}}";
            String docTemplate = "{\"doc\":{},\"doc_as_upsert\" : true}";
            StrBuilder strBuilder = new StrBuilder();

            DateTime current = DateUtil.date();

            for (int i = 0; i < dataArray.size(); i++) {
                JSONObject docObject = dataArray.getJSONObject(i);
                if (docObject == null) {
                    continue;
                }
                String nodeId = docObject.getStr("nodeId");
                String meta = StrUtil.format(metaTemplate, index, type, nodeId);
                String doc = StrUtil.format(docTemplate, docObject.toString());
                strBuilder.append(meta).append("\r\n").append(doc).append("\r\n");
            }

            String resultJSON = HttpRequest.post(postUrl)
                    .header("Connection", "close")
                    .keepAlive(false)
                    .body(strBuilder.toString())
                    .timeout(20000)//超时，毫秒
                    .execute().body();
            boolean isJSON = JSONUtil.isJson(resultJSON);
            if (!isJSON) {
                throw new RuntimeException(StrUtil.format("ES查询异常,返回结果异常"));
            }
        });
        log.info("es node更新中...");
    }



}
